Repository navigation
fix(speakers): defer short live epoch labels and gate owner evidence - #23533
Conversation
…diarization ids
A live episode's "So far" bullets read "Speaker 379 mentions..." because the
legacy live-moment renderer wrote the raw per-conversation diarization counter
into the prompt ("Speaker 379: ...") and the summarizer copied the label
verbatim into user-visible notes (#23381). The finished-notes v2 path already
renders cluster keys, so it never had the problem.
Add utils/speaker_display.SpeakerLabeler and route every LLM-facing or
user-facing transcript renderer through it: the owner renders as "You" or the
owner's name (live moments now resolve the account's display name), named
people keep their names, and every unnamed voice gets a dense per-transcript
"Speaker A/B/..." label in first-appearance order, keyed by
(speaker_id_scope, speaker_id) so merged multi-source transcripts stay
distinct. Raw diarization ids no longer reach any prompt or payload:
- live moments: incremental.speaker_label/_legacy_moment (+ moment rules now
forbid copying voice markers or placeholder headings like "New moment")
- TranscriptSegment.segments_as_string (titles, memories, summaries, chat)
- transcript_for_llm action-item speaker labels
- shared-chat bounded transcripts, smart-merge question excerpts, retrieval
chunks and MCP match snippets, review quotes
- conversation analytics and populate_speaker_names (MCP/integration/webhook
payloads)
Bump smart-merge QUESTION_VERSION: cached adjacent-merge answers were keyed to
the old raw-id excerpts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two guards so "Speaker 379" cannot come back:
- unit: run each LLM-facing formatter with the incident's raw ids (378/379,
across scopes) and assert the digits never survive a render; covers
segments_as_string, live and v2 moment renderers, cluster transcripts,
smart-merge lines, shared-chat bounded transcripts, MCP snippets, retrieval
chunks, action-item labels, populate_speaker_names and analytics.
- static: check_speaker_label_interpolation.py rejects any new interpolated
"Speaker {speaker_id}" (f-string, .format, %, concatenation) outside the
sanctioned SpeakerLabeler; registered in checks-manifest with a self-test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n's evidence A People record, lexicon spelling, profile, memory or old meeting can no longer authorize a participant name. Calendar attendees overlapping the conversation, this conversation's roster/tile names and affirmative owner corrections still do. Enforced in the persisted notes contract independent of packet rollout; agent output is repaired, unsupported names become "a speaker" or hedged heard names. Failure-Class: notes-identity-leak Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load the real speaker presentation modules in the isolated date-validation harness, expect canonical c0 keys in wake-word rendering, and remove the duplicate Dart test import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ping Failure-Class: FC-generated-presentation-contract-bypass Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Failure-Class: FC-generated-presentation-contract-bypass Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve trusted manual episode name projections without using a bound excerpt name as the fallback label for unbound speech of that provider voice. Failure-Class: FC-generated-presentation-contract-bypass Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y light notes_packet imported known_spellings at module scope, pulling the dream store and People database into every conversation_processing import; the isolated action-item test then failed at collection. Import it at the call, and pre-load the speaker display modules the models now import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load the real known-spelling helper with explicit empty database adapters so notes-packet imports do not pull the dream-agent runtime into action-item date tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deduplicate named participants by People record ID so repeated diarization IDs count once while different people with the same display name retain separate participant counts. Preserve the existing transcript heading regression assertion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The published notes packet repair now defers its database-dependent spelling import until it is used. Keep the date harness minimal instead of eagerly loading the helper with extra database mocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the identity-aware display authority at overlapping renderers; preserve main boundary regression coverage in its own suite, both static scan interfaces and the legacy scoped-key helper. Remove stale labeler scaffolding and keep analytics compatible with lightweight segment objects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the active worker count in the parent shell and update it with the existing PID arrays. Every 20ms polling iteration previously forked twice for command substitution merely to count workers. File isolation, status handoff, crash detection and timing budgets remain intact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canonical identity authority passes the full raw-speaker scan without an extra exemption. Keep main full-scan coverage unchanged alongside the broader changed-file identity ratchet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtion Main changed this regression to render all segments in one transcript. Retain its stronger A/B and six-distinct-output assertions; the old per-segment A/A expectation was kept incorrectly during the speaker-authority merge. Digest and attribution checks remain intact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the single module-scoped payment fixture and its per-test mock reset from current main. Accept the optional speaker-rendering keyword in pagination IO stubs. Keep every payment and pagination assertion intact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Accept the explicit preserve_api_values keyword in the offline external-list route speaker-name stub. The prerequisite calls this mode for released developer API identity compatibility; the real route assertions stay unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain both independent flag declarations and generate the shared catalog. Preserve the speaker-order reset and the new offline-sync episode regressions together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the default-on notes kill switch SPEAKER_IDENTITY_REPAIR_ENABLED so its generated registry entry is separate from concurrently merged flags. Move the existing episode speaker-order test away from the insertion point used by the offline-sync tests. Keep the same behavior and assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the explicit optional speaker-rendering keyword contract while integrating the prerequisite equivalent fixture fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the real pure capture evidence modules from current main together with the speaker presentation modules from the prerequisite inside the isolated action-item fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the current-main capture-evidence imports alongside the speaker presentation imports in the isolated action-item date fixture. Retain every date-validation assertion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb9c61fd4b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if speaker is not None and speaker in named: | ||
| return named[speaker] == name.casefold() |
There was a problem hiding this comment.
Honor confirmed identity corrections before stale speaker bindings
When an owner corrects a misidentified speaker, this early return rejects the corrected name before consulting corrections. For example, with c1 → Michael Liebmann and the confirmed correction Speaker c1 is Phong, not Michael Liebmann., an agent submission naming Phong for c1 loses its participant and has Phong scrubbed from its title and prose. This can occur during reprocessing because update_conversation stores durable correction notes without rewriting transcript bindings, and confirmed_corrections_context explicitly tells the writer to use those corrected facts. Allow an applicable owner-confirmed correction to override the stale binding while continuing to reject conflicting calendar leads.
Useful? React with 👍 / 👎.
Retain all existing capture and offline-sync coverage while accepting the prerequisite speaker regression relocation and default-on switch rename. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the new receipt and recap cache regressions, reset the cache per test, and preserve offline speaker-name isolation in its own fixture away from shared setup insertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep speaker-name IO stubs in a separate autouse fixture so current main can add its reset and receipt setup without conflicting. Preserve the same test behavior and all assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the prerequisite separate speaker-name fixture once, preserving the latest main cache reset and receipt helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8b97bd7ca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| display_participants( | ||
| [s for s in segments if isinstance(s, dict)], resolution=conversation.get('speaker_resolution') | ||
| ) |
There was a problem hiding this comment.
Share resolved names across MCP participant and transcript projections
On /v1/mcp/conversations/{conversation_id}, populate_speaker_names resolves contact names before building the response, but this participant projection omits those names while bounded_transcript_segments passes them into its projection. Consequently, the same voice is labeled Speaker A in participants and Ada in transcript_segments. With more than eight voices, the disagreement also breaks speaker_key joins: a short named voice can receive others here while retaining c0 in the transcript because the transcript projection protects named people from collapse. Build both response sections from the same name-aware projection so labels, collapse decisions, and voice keys agree.
Useful? React with 👍 / 👎.
Retain the tested live-epoch feature files byte-for-byte while accepting main fixture implementations. All runtime code is unchanged from the previously qualified head; the dependency is now merged upstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep short continuity accepts out of cross-scope competition until both voices have the grouping evidence floor, while preserving same-scope contention and strict acoustic groups. Cover the reconnect plus idle-reopen owner regression and policy boundaries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…23556) #23533 (85f080f) defers a live "Speaker N" ordinal until an unnamed voice holds five seconds of non-overlapping placed speech, but nothing measured whether per-session ordinal churn actually fell after the listen roll - David's consecutive Speaker 4-8 lines had no metric. Add omi_live_speaker_ordinals_per_session, a histogram observed once per listen session at teardown. LiveSpeakerOrdinalTracker rides the same final segment stream the collapse monitor sees (routers/listen/transcripts _speaker_detection, after scope stamping and speaker-id assignment) and applies the display deferral's own qualification: a voice key (speaker_id_scope, speaker_id) mints one ordinal at MIN_EVIDENCE_SECONDS of unioned placed speech, and a voice the session named (owner or person, per-segment or in the matcher's final map) never mints one. The companion omi_live_speaker_scope_rotations_total counts scope/epoch rotations over the same sessions, so ordinal churn is separable from real multi-speaker conversations. Both metrics are label-free; the tracker is bounded (128 voices, 512 intervals per voice), never feeds speaker decisions, and its recording never raises into teardown. The pipeline doc's live-speaker-epochs section names the PromQL to watch after listen rolls. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Short Soniox epochs produced consecutive Speaker 4–8 labels because provider/socket scopes each reserve a new conversation voice. This PR ships the bounded safe repair: live unknown voices spend no visible ordinal until five seconds of non-overlapping placed speech. Names still come from existing manual/acoustic authority. Raw identities stay distinct and available for correction.
This stacked PR depends on #23462 (
fix/speaker-identity-guardrails, integrated head072a983314bfb4e24a376b64076fe168ab416fe3) and must merge after #23462. #23404 is already merged to main as6243071d9efeff253570749da4cece26ffcc7943. This branch merges the reconciled prerequisite and main, with no rebase or force push, and targets main as requested. It extends their shared display authorities rather than introducing another labeler.SpeakerNames' live qualification floor; qualified voices number in qualification order. Short/unplaced voices show the localized Speaker, with owner and person names preserved.CROSS_EPOCHthreshold,MATCH_MARGIN, complete-link agreement and different scopes. Weak/ambiguous vectors never merge competitors; short continuity accepts are provisional for cross-scope arbitration until they have five seconds of evidence. This grouping remains internal to owner arbitration; it does not rewrite transcript identities.The prerequisite has now merged to main as
9fdfa45cea718a131e9f4186e30e5b241579c66a; the merge-after dependency is satisfied. Its deleted branch is represented by the verified final PR head above and the merged main commit.Owner arbitration repair
A listen reconnect followed by an idle reopen can produce six seconds of owner evidence in epoch a and a two-second continuity accept in epoch b. Both voices now retain “You” while the short voice remains acoustically ungrouped. Provisional voices defer cross-scope competition until both reach five seconds; same-scope competitors and manual reservations still constrain claims. No failed voiceprint decision is promoted. All three live arbitration paths use the same scope/evidence context, including carry and owner revalidation.
The asserted reviewer probe fails twice before the fix and passes both variants after it, including actual
Youdisplay labels and owner revalidation. The strict five-second grouping, margin, complete-link and unnamed-display policies are unchanged. Policy regressions cover 4.99/5 seconds, unknown/same scopes, manual reservation, invalid evidence and a same-scope competitor against a multi-epoch owner group.Scope and remaining work
This is the authorized safe subset, not complete anonymous live linking. Anonymous voices with substantial evidence in multiple epochs can still acquire separate ordinals. Cross-listen anonymous identity needs a bounded encrypted, model/version/provenance-bound evidence handoff and reversible display aliases fenced by manual receipt generation, replica concurrency and finalization. The implementation/qualification checklist is in
backend/docs/listen_pusher_pipeline.mdx, “Remaining live anonymous linking”. Unplaced audio still bypasses matching; capture placement belongs to the timeline lane. No provider-label equality or weak short-turn vectors force an owner match.Prerequisite review follow-ups
The refreshed prerequisite fixes catalog-only ordinary-word contact scrubbing. One review finding remains in files unchanged from #23462: partially named/owner voices can be counted twice in the transcript heading. It is an inherited main follow-up; this live-epoch change leaves those files untouched. The live ledger finding is fixed here with an incremental-renderer regression.
Failure-Class: FC-generated-presentation-contract-bypass
Line-Count-Exception: app/lib/widgets/transcript.dart | 1589 -> 1590 | Pass the live qualification option into the existing shared speaker naming primitive; no new widget behavior or duplicated labeler.
Product invariants affected
SpeakerNamesremains the Flutter naming authority. Live short-voice deferral extends that primitive and is documented inapp/docs/ux-contract.md; distinct wire/correction identities and named-participant counts remain intact.Trusted manual
person_nameprojections remain visible, while a name bound to one excerpt never labels an unbound excerpt from the same provider voice. Focused regressions cover both boundaries.Validation
65fdab94c8b45e5df54230875c4c82a879fc4b84; mergedorigin/mainatc28475014e0dbefore the fix. Both probe variants pass, with 31/31 reconnect tests and 229 additional focused policy/epoch/carry/recovery/wire/prior tests passing. No real-audio or production acceptance is claimed.65fdab94c8b45e5df54230875c4c82a879fc4b84; 57 terminal checks, zero failures or pending, with repository-selected skips. All six backend shards, the backend aggregate, Dart analysis/tests, mobile eligibility, hermetic backend E2E and listen-pusher gauntlet passed without reruns. Local repository and metadata preflight passed all 47 checks; the normal pre-push gate and backend type checking passed. Pushed with SSH keepalives and verified bygit ls-remote. The PR remains open and unmerged; no GCP tier used.The following broader suite and scorecard results were recorded on the prior head; they are retained as prior validation, not claimed as final-head runs.
Final publication source:
7d400b81792eb3480ff95a260f06693265c7b8f5; merged main9fdfa45cea718a131e9f4186e30e5b241579c66aand fix(speakers): add typed identity, count and presentation guardrails #23462072a983314bfb4e24a376b64076fe168ab416fe3.Backend: 571 tests across 24 focused files passed, including all 13 live epoch/moment cases and all 40 external-list route cases. The external-list offline speaker-name stub accepts the prerequisite rendering keyword; the real route assertions are unchanged. Ledger regressions cover short/unplaced replies, a new epoch and conversation isolation.
Flutter: 106 tests across five focused files passed, covering naming, rendered transcripts, episode UI/provider state and previous-note navigation. The current complete Dart analysis is covered by the PR checks.
Web: 154 files / 874 tests passed in the full suite. The web task-row regression now waits for Motion's post-render hover callback before double-clicking the snooze control. Its assertions and product behavior are unchanged; the focused two-test file passed.
Local
scripts/pr-preflightpassed all 47 checks in the normal push hooks; PR metadata passed all five checks. Normal push hooks run before publication. A final main/prerequisite refresh is followed by focused backend/Flutter rechecks and frozen score replay; publication stops if the refreshed metrics differ.Remote CI: exact published head
7d400b81792eb3480ff95a260f06693265c7b8f5is green: all executed checks passed, with repository-selected skips and zero failures/pending. All six backend shards, backend aggregate, Dart analysis/tests, mobile aggregate and hermetic merge gate passed. The listen-pusher gauntlet passed its single targeted rerun after a 15-second localhost HTTP read timeout in the existing concurrent REST-finalization restart scenario; no speaker assertion failed. No backend shard budget rerun was needed.The acceptance fixture drives the real Soniox idle close/reopen adapter, epoch stamper and conversation allocator, deterministic short-turn embeddings through the live matcher, and a fresh listen matcher hydrated from serialized rows. After reconnect it swaps provider labels between owner and guest. Both enrolled and unenrolled guest cases must re-acquire evidence; an enrolled guest keeps the same name, an unenrolled guest stays separate and unnamed until qualification. Owner recognition never follows provider-number equality. These tests qualify the bounded safe repair; anonymous identity continuity across replicas remains deferred. No device or production acceptance is claimed.
Frozen local-quality scorecard
Executed the frozen #23435 scorer (
e7a2144f63edtooling) with the unchanged private corpus/word clocks, SHA-256b2d293306c74e1423ae41c70d0432ad5eea012a60224cf6f7dc88284105309cc. Historical inference source/audio/crop/payload hashes were verified, then this branch's realProjector.assign_parts, projection builder and conversation allocator ran with filesystem store/assignment boundaries. The split diagnostic uses the frozen split validator and the candidate's production split/build/allocation code. No raw identity mapping was fitted and no display collapse was counted as diarization improvement.Engineering gate: FAIL. All 10,541 scored words remain anonymous in this batch path: owner/enrolled accuracy is 0/4,415 (0%); wrong-name/false-owner rate is 0%. The live matcher is not exercised by this batch scorecard. This branch does not include the separate #23436 naming or #23437 batch voice-link changes that produced the supplied earlier 6/10/28/28/54 comparison, so the count differences (0/0/-1/-3/-3) are not like-for-like improvements attributable to this live repair. The batch identity producer files are byte-identical to main at evaluation.
The frozen five-speaker split case produced 27 unsplit speakers and 37/30 after the two cuts. Identity loss is 0.0/0.0 percentage points, versus the earlier approximately 3.6–3.8 points. Both split and unsplit owner accuracy are zero here: this is an all-anonymous floor, not a continuity gain, and the split gate remains FAIL. Anonymous cross-listen linking remains deferred as described above.
This was a current-projection replay with zero new model calls, zero production reads and no Firestore transaction qualification. Original inference receipts are retained; seven historical worker switches remain UNKNOWN, so full current-worker inference compatibility is INCOMPLETE. Non-English and real-device/production acoustic acceptance were not evaluated. Private persisted outputs, source hashes and aggregate reports remain outside Git.
UI-Evidence: none -- Naming policy changes only; focused naming and rendered transcript tests cover the visible labels. No layout or styling change.
Delivery
Backend/listen image deployment and a Flutter build are required for the corresponding changes. No merge, deploy, production data read, api.omi.me call, production config flip, or GCP tier used. No language-gate or firmware file touched. Rollback: revert the live-epoch feature changes while retaining the prerequisite PRs.
🤖 Generated with Claude Code